﻿using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using MathExpression;

namespace SimplexSolver
{
    public class SimplexTableBuilder
    {
        public static SimplexTable Create(ILPTask task)
        {
            var restrictions = new List<Restriction>(task.Restrictions);
            VariableType[] additionalVariablesCount = GetAdditionalVariablesCount(restrictions);
            IMathExpression[][] restrictionMatrix = CreateRestrictionMatrix(restrictions, additionalVariablesCount);
            IMathExpression[] freeValues = CreateFreeValueColumn(restrictions);
            IMathExpression[] targetRow = CreateTargetRow(task.Target, additionalVariablesCount);
            var table = new SimplexTable(targetRow, freeValues, restrictionMatrix);
            int mainVariablesCount = restrictions[0].DataRow.Count;
            SetSimplexTableHeader(table, mainVariablesCount, additionalVariablesCount);
            return table;
        }

        public static SimplexTable CreateSecondBasis(SimplexTable table)
        {
            var targetRow = new IMathExpression[table.TargetRow.Length];
            for (int i = 0; i < targetRow.Length; i++)
            {
                targetRow[i] = ExpressionFactory.Create(0.0);
            }
            IMathExpression[] freeValues = table.FreeValueColumn;
            IMathExpression[][] matrix = table.RestrictionMatrix;
            string[] header = table.RowHeaders;
            int count = header.Length;
            IMathExpression value = ExpressionFactory.Create(0.0);
            for (int i = 1; i < count; i++)
            {
                if (header[i][0] == 'z')
                {
                    for (int j = 0; j < targetRow.Length; j++)
                    {
                        targetRow[j] = targetRow[j].Add(matrix[i - 1][j]);
                    }
                    value = value.Add(freeValues[i - 1]);
                }
            }
            var newTable = new SimplexTable(targetRow, freeValues, matrix, value) { ColumnHeaders = table.ColumnHeaders, RowHeaders = table.RowHeaders };
            return newTable;
        }

        public static SimplexTable CreateFromSecondBasis(IMathExpression[] oldTarget, SimplexTable secondBasisTable)
        {
            var table = new SimplexTable(oldTarget, secondBasisTable.FreeValueColumn, secondBasisTable.RestrictionMatrix)
            {
                ColumnHeaders = secondBasisTable.ColumnHeaders,
                RowHeaders = secondBasisTable.RowHeaders
            };
            IMathExpression[][] oldRestrictionMatrix = table.RestrictionMatrix;

            IList<int> secondBasisIndexes = GetSecondBasisColumnIndexes(table);
            int mainVarsCount = oldTarget.Length - secondBasisIndexes.Count;
            int restrictionsCount = oldRestrictionMatrix.Length;
            var restrictionMatrix = new IMathExpression[restrictionsCount][];
            var target = new IMathExpression[mainVarsCount];

            for (int i = 0; i < restrictionsCount; i++)
            {
                int index = 0;
                restrictionMatrix[i] = new IMathExpression[mainVarsCount];
                for (int j = 1; j < table.ColumnsCount; j++)
                {
                    if (secondBasisIndexes.Contains(j))
                    {
                        continue;
                    }
                    restrictionMatrix[i][index] = oldRestrictionMatrix[i][j - 1];
                    if (i == 0)
                    {
                        target[index] = oldTarget[j - 1];
                    }
                    index++;
                }
            }
            var columnHeader = new List<string>();
            for (int i = 0; i < table.ColumnHeaders.Length; i++)
            {
                if (!secondBasisIndexes.Contains(i))
                {
                    columnHeader.Add(table.ColumnHeaders[i]);
                }
            }
            return table;
        }

        private static IList<int> GetSecondBasisColumnIndexes(SimplexTable table)
        {
            var secondBasisIndexes = new List<int>();
            string[] header = table.ColumnHeaders;
            for (int i = 0; i < header.Length; i++)
            {
                if (header[i][0] == 'z')
                {
                    secondBasisIndexes.Add(i);
                }
            }
            return secondBasisIndexes;
        }

        private static void SetSimplexTableHeader(SimplexTable table, int mainVariablesCount, VariableType[] additionalVariablesCounts)
        {
            int additionCounter = 0;
            SetSimplexTableRowHeader(table, additionalVariablesCounts, ref additionCounter);
            SetSimplexTableColumnHeader(table, mainVariablesCount, additionCounter);
        }

        private static void SetSimplexTableColumnHeader(SimplexTable table, int mainVariablesCount, int additionCounter)
        {
            int mainCounter = 0;
            int columnCount = table.ColumnHeaders.Length;
            for (int i = 1; i < columnCount; i++)
            {
                string header;
                if (i < mainVariablesCount + 1)
                {
                    header = "x" + mainCounter.ToString(CultureInfo.InvariantCulture);
                    mainCounter++;
                }
                else
                {
                    header = "y" + additionCounter.ToString(CultureInfo.InvariantCulture);
                    additionCounter++;
                }
                table.ColumnHeaders[i] = header;
            }
        }

        private static void SetSimplexTableRowHeader(SimplexTable table, IList<VariableType> additionalVariablesCounts, ref int additionCounter)
        {
            int secondBasisCounter = 0;
            int rowCount = table.RowHeaders.Length;
            for (int i = 1; i < rowCount; i++)
            {
                string header;
                if (additionalVariablesCounts[i - 1] == VariableType.Additional)
                {
                    header = "y" + additionCounter.ToString(CultureInfo.InvariantCulture);
                    additionCounter++;
                }
                else
                {
                    header = "z" + secondBasisCounter.ToString(CultureInfo.InvariantCulture);
                    secondBasisCounter++;
                }
                table.RowHeaders[i] = header;
            }
        }

        private static IMathExpression[] CreateTargetRow(TargetFunction target, IEnumerable<VariableType> additionalVariablesCount)
        {
            int targetCount = target.Coefs.Count;
            int additionalSum = additionalVariablesCount.Count(type => type == VariableType.All);
            int columnCount = targetCount + additionalSum;

            var targetRow = new IMathExpression[columnCount];
            IMathExpression minus = ExpressionFactory.Create(-1.0);
            for (int i = 0; i < columnCount; i++)
            {
                if (i < targetCount)
                {
                    targetRow[i] = target.Coefs[i].Multiply(minus);
                }
                else
                {
                    targetRow[i] = ExpressionFactory.Create(0.0);
                }
            }
            return targetRow;
        }

        private static IMathExpression[] CreateFreeValueColumn(IList<Restriction> restrictions)
        {
            int rowCount = restrictions.Count;
            var freeValues = new IMathExpression[rowCount];
            for (int i = 0; i < rowCount; i++)
            {
                freeValues[i] = restrictions[i].Limit.Clone();
            }
            return freeValues;
        }

        private static IMathExpression[][] CreateRestrictionMatrix(IList<Restriction> restrictions, IList<VariableType> additionalVariablesCount)
        {
            int additionalVariablesSum = additionalVariablesCount.Count(type => type == VariableType.All);
            int columnCount = restrictions[0].DataRow.Count + additionalVariablesSum;
            int rowCount = restrictions.Count;
            var restrictionMatrix = new IMathExpression[rowCount][];
            for (int i = 0; i < rowCount; i++)
            {
                restrictionMatrix[i] = new IMathExpression[columnCount];
                for (int j = 0; j < columnCount; j++)
                {
                    IMathExpression value = ExpressionFactory.Create(0.0);
                    if (j < restrictions[i].DataRow.Count)
                    {
                        value = restrictions[i].DataRow[j].Clone();
                    }
                    else if (additionalVariablesCount[i] == VariableType.All)
                    {
                        value = ExpressionFactory.Create(-1.0);
                    }
                    restrictionMatrix[i][j] = value;
                }
            }
            return restrictionMatrix;
        }

        private static VariableType[] GetAdditionalVariablesCount(IList<Restriction> restrictions)
        {
            var additionalVariablesCount = new VariableType[restrictions.Count];
            for (int i = 0; i < restrictions.Count; i++)
            {
                if (restrictions[i].IneqMode == Restriction.EIneqMode.LessOrEqual)
                {
                    additionalVariablesCount[i] = VariableType.Additional;
                }
                else if (restrictions[i].IneqMode == Restriction.EIneqMode.Equal)
                {
                    additionalVariablesCount[i] = VariableType.SecondBasis;
                }
                else
                {
                    additionalVariablesCount[i] = VariableType.All;
                }
            }
            return additionalVariablesCount;
        }

        private enum VariableType
        {
            Additional,
            SecondBasis,
            All
        }
    }
}